package com.mazaiting.rtda.heap;

import cn.hutool.core.util.ObjectUtil;
import com.mazaiting.classfile.ClassFile;
import com.mazaiting.classfile.constant.ConstantPool;
import com.mazaiting.classpath.ClassResponse;
import com.mazaiting.classpath.Classpath;
import com.mazaiting.rtda.MtSlots;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;

/**
 * 类加载器
 * 依赖Classpath来搜索和读取class文件
 *
 * @author mazaiting
 * @since 2021/8/16
 */
@Slf4j
public class MtClassloader {
    /**
     * 类路径
     */
    private final Classpath classpath;
    /**
     * classMap字段记录已经加载的类数据，key是类的完全限定名
     * 可以把classMap字段当作方法区的具体实现
     */
    private final Map<String, MtClass> classMap;
    /**
     * 是否把类加载信息输出到控制台
     */
    private final boolean verboseFlag;

    public MtClassloader(Classpath classpath, boolean verboseClassFlag) {
        this.classpath = classpath;
        this.classMap = new HashMap<>();
        this.verboseFlag = verboseClassFlag;
    }

    /**
     * 加载类
     *
     * @param name 类名
     * @return 类实例
     */
    public MtClass loadClass(String name) {
        MtClass mtClass = classMap.get(name);
        // 判断类是否加载
        if (ObjectUtil.isNotNull(mtClass)) {
            return mtClass;
        }
        return loadNonArrayClass(name);
    }

    /**
     * 加载非数组类实例
     * 数组类和普通类有很大的不同，它的数据并不是来自class文件，而是由Java虚拟机在运行期间生成。
     *
     * @param name 类名
     * @return 类实例
     */
    private MtClass loadNonArrayClass(String name) {
        // 找到class文件并把数据读取到内存
        ClassResponse response = readClass(name);
        // 解析class文件，生成虚拟机可以使用的类数据，并放入方法区
        MtClass mtClass = defineClass(response.getBytes());
        // 链接
        linkClass(mtClass);
        if (verboseFlag) {
            System.out.printf("[Loaded %s from %s]\n", name, response.getEntry());
        }
        return mtClass;
    }

    /**
     * 找到class文件并把数据读取到内存
     *
     * @param name 类名
     * @return 类响应
     */
    private ClassResponse readClass(String name) {
        ClassResponse response = classpath.readClass(name);
        if (ObjectUtil.isNotEmpty(response.getError())) {
            throw new RuntimeException("java.lang.ClassNotFoundException: " + name);
        }
        return response;
    }

    /**
     * 解析class文件，生成虚拟机可以使用的类数据，并放入方法区
     *
     * @param data 类的二进制数据
     * @return 类实例
     */
    private MtClass defineClass(byte[] data) {
        // 调用parseClass()函数把class文件数据转换成Class结构体
        MtClass mtClass = parseClass(data);
        mtClass.setLoader(this);
        // 超类名
        resolveSuperClass(mtClass);
        // 接口名
        resolveInterfaces(mtClass);
        this.classMap.put(mtClass.getName(), mtClass);
        return mtClass;
    }

    /**
     * 解析类
     *
     * @param data 类的二进制数据
     * @return 类实例
     */
    private MtClass parseClass(byte[] data) {
        return new MtClass(ClassFile.parse(data));
    }

    /**
     * 加载超类
     *
     * @param mtClass 类实例
     */
    private void resolveSuperClass(MtClass mtClass) {
        // 除 java.lang.Object 以外，所有的类都有且仅有一个超类
        if (ObjectUtil.notEqual("java/lang/Object", mtClass.getName())) {
            mtClass.setSuperClass(mtClass.getLoader().loadClass(mtClass.getSuperClassName()));
        }
    }

    /**
     * 加载接口
     *
     * @param mtClass 类实例
     */
    private void resolveInterfaces(MtClass mtClass) {
        // 获取接口数量
        int length = mtClass.getInterfaceNames().length;
        if (length > 0) {
            mtClass.setInterfaces(new MtClass[length]);
            // 遍历
            for (int i = 0; i < length; i++) {
                String name = mtClass.getInterfaceNames()[i];
                // 设置接口名
                mtClass.getInterfaces()[i] = mtClass.getLoader().loadClass(name);
            }
        }
    }

    /**
     * 链接类
     * 验证和准备
     *
     * @param mtClass 类实例
     */
    private void linkClass(MtClass mtClass) {
        verify(mtClass);
        prepare(mtClass);
    }

    /**
     * 验证类字节码
     *
     * @param mtClass 类实例
     */
    private void verify(MtClass mtClass) {
        // TODO 待做 JVM 4.10
    }

    /**
     * 准备类
     *
     * @param mtClass 类实例
     */
    private void prepare(MtClass mtClass) {
        calcInstanceFieldSlotIds(mtClass);
        calcStaticFieldSlotIds(mtClass);
        allocAndInitStaticVars(mtClass);
    }

    /**
     * 计算实例字段
     *
     * @param mtClass 类实例
     */
    private void calcInstanceFieldSlotIds(MtClass mtClass) {
        int slotId = 0;
        // 判断父类是否为空, 不为空则初始化实例字段
        if (ObjectUtil.isNotNull(mtClass.getSuperClass())) {
            slotId = mtClass.getInstanceSlotCount();
        }
        // 遍历类字段
        for (int i = 0; i < mtClass.getFields().length; i++) {
            MtField field = mtClass.getFields()[i];
            if (!field.isStatic()) {
                field.setSlotId(slotId++);
                // 如果是占两个字节的数, 再+1
                if (field.isLongOrDouble()) {
                    slotId++;
                }
            }
        }
        // 设置实例变量数
        mtClass.setInstanceSlotCount(slotId);
    }

    /**
     * 计算静态字段
     *
     * @param mtClass 类实例
     */
    private void calcStaticFieldSlotIds(MtClass mtClass) {
        int slotId = 0;
        for (int i = 0; i < mtClass.getFields().length; i++) {
            MtField field = mtClass.getFields()[i];
            if (field.isStatic()) {
                field.setSlotId(slotId++);
                if (field.isLongOrDouble()) {
                    slotId++;
                }
            }
        }
        // 设置静态变量数
        mtClass.setStaticSlotCount(slotId);
    }

    /**
     * 分配和初始化静态变量
     *
     * @param mtClass 类实例
     */
    private void allocAndInitStaticVars(MtClass mtClass) {
        mtClass.setStaticVars(new MtSlots(mtClass.getStaticSlotCount()));
        for (MtField field : mtClass.getFields()) {
            // 如果是最终字段与 static 字段
            if (field.isStatic() && field.isFinal()) {
                initStaticFinalVar(mtClass, field);
            }
        }
    }

    /**
     * 初始化静态与不可变变量
     *
     * @param mtClass 类实例
     * @param field   字段
     */
    private void initStaticFinalVar(MtClass mtClass, MtField field) {
        MtSlots staticVars = mtClass.getStaticVars();
        MtConstantPool pool = mtClass.getConstantPool();
        int constValueIndex = field.getConstValueIndex();
        int slotId = field.getSlotId();
        if (constValueIndex > 0) {
            switch (field.getDescriptor()) {
                case "Z":
                case "B":
                case "C":
                case "S":
                case "I":
                    staticVars.setInt(slotId, ((MtConstant.MtIntConstant) pool.getConstant(constValueIndex)).getValue());
                    break;
                case "J":
                    staticVars.setLong(slotId, ((MtConstant.MtLongConstant) pool.getConstant(constValueIndex)).getValue());
                    break;
                case "F":
                    staticVars.setFloat(slotId, ((MtConstant.MtFloatConstant) pool.getConstant(constValueIndex)).getValue());
                    break;
                case "D":
                    staticVars.setDouble(slotId, ((MtConstant.MtDoubleConstant) pool.getConstant(constValueIndex)).getValue());
                    break;
                case "Ljava/lang/String;":
                    //case "L":
                    //staticVars.setRef(slotId, constValueIndex > 0 ? ((MtConstant.MtClassRef) pool.getConstant(constValueIndex)):0.0D);
                    //staticVars.setRef(slotId, null);
                    break;
                default:
                    break;
            }
        } else {
            switch (field.getDescriptor().substring(0, 1)) {
                case "Z":
                case "B":
                case "C":
                case "S":
                case "I":
                    staticVars.setInt(slotId, 0);
                    break;
                case "J":
                    staticVars.setLong(slotId, 0L);
                    break;
                case "F":
                    staticVars.setFloat(slotId, 0.0F);
                    break;
                case "D":
                    staticVars.setDouble(slotId, 0.0D);
                    break;
                case "L":
                    staticVars.setRef(slotId, null);
                    break;
                default:
                    break;
            }
        }
    }
}









